package com.alpha.processor.creators.connector_file;

import com.alpha.binder.anno.motion.OnViewClick;
import com.alpha.binder.interfaces.ConnectorInterface;
import com.alpha.processor.creators.info_model.ProcessorInfoModel;
import com.alpha.processor.creators.interface_file.ProviderInterfaceCreateModel;
import com.alpha.processor.processor.tools.ProcessorTool;
import com.alpha.processor.creators.base.FileCreator;
import com.alpha.processor.creators.info_model.ViewInfoModel;
import com.alpha.processor.util.ParseNameUtil;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.util.Map;

import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;


public class ConnectorFileCreator extends FileCreator {

    private final ConnecteCreateModel createModel;

    public ConnectorFileCreator(ProcessorTool logger, Elements elementUtils, TypeElement classElement) {
        createModel = new ConnecteCreateModel(logger,elementUtils,classElement);
    }

    @Override
    public String getFilePackageName() {
        return createModel.getPackageName();
    }

    @Override
    public String getGenerateFileName() {
        return createModel.getGenarateFileName();
    }

    @Override
    public String getGenerateFileFullName() {
        return createModel.getGenerateFileFullName();
    }

    @Override
    public Element getClassElement() {
        return createModel.getClassElement();
    }

    @Override
    protected void buildFile(TypeSpec.Builder fileBuilder) {

        ViewInfoModel viewInfoModel = createModel.getViewInfoModel();
        ProcessorInfoModel processorInfoModel = createModel.getProcessorInfoModel();

        boolean haveProvider = createModel.getProviderCreateModel() != null;
        boolean haveEvents = viewInfoModel.getClickMethodElementMap().size() > 0;

        fileBuilder.addModifiers(Modifier.PUBLIC);
        fileBuilder.addSuperinterface(ClassName.bestGuess(createModel.getConnectorInfoModel().getInterfaceName()));
        if (haveEvents){
            fileBuilder.addSuperinterface(ClassName.bestGuess(createModel.getConnectorInfoModel().getL_Name()));
        }

        ClassName targetClassName = ClassName.bestGuess(createModel.getClassName());
        fileBuilder.addField(targetClassName, "clazz");
        fileBuilder.addField(ClassName.bestGuess(createModel.getConnectorInfoModel().getK_Name()), "pageRootView");
        if (haveProvider){
            fileBuilder.addField(ClassName.bestGuess(createModel.getProviderCreateModel().getGenarateFileName()), "paramProvider");
        }

        fileBuilder.addMethod(generateInitMethod(targetClassName,haveEvents,haveProvider));
        fileBuilder.addMethod(generateGetRootViewMethod());
        fileBuilder.addMethod(generateGetClickListenerMethod(haveEvents));

        fileBuilder.addMethod(generateBindViewMethod(viewInfoModel));
        if (haveProvider){
            fileBuilder.addMethod(generateBindProcessorMethod(processorInfoModel));
        }

        if (haveEvents){
            fileBuilder.addMethod(generateBindEventMethod(viewInfoModel));
            fileBuilder.addMethod(generateOnClickMethod(viewInfoModel));
        }

        fileBuilder.addMethod(generateDisposeMethod(viewInfoModel,processorInfoModel,haveProvider));
    }

    // Implement Method --- init()
    private MethodSpec generateInitMethod(ClassName targetClassName,boolean haveEvents,boolean haveProvider) {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ConnecteCreateModel.InitMethodName)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(Object.class, "clazz")
                .addParameter(ClassName.bestGuess(createModel.getConnectorInfoModel().getT_Name()), "inflator")
                .addParameter(ClassName.bestGuess(createModel.getConnectorInfoModel().getK_Name()), "rootView")
                .returns(ClassName.bestGuess(createModel.getConnectorInfoModel().getInterfaceName()));

        methodBuilder.addCode("this.clazz = ($T)clazz;\n",targetClassName);
        if (haveProvider){
            methodBuilder.addCode("this.$N = new $T();\n","paramProvider",ClassName.bestGuess(createModel.getProviderCreateModel().getClassName()));
        }

        methodBuilder.addCode("$N(inflator,rootView);\n",ConnecteCreateModel.BindViewsMethodName);

        if (haveProvider){
            methodBuilder.addCode("$N();\n",ConnecteCreateModel.BindProcessorMethodName);
        }

        if (haveEvents){
            methodBuilder.addCode("$N();\n",ConnecteCreateModel.BindEventsMethodName);
        }

        methodBuilder.addCode("return this;\n");
        return methodBuilder.build();
    }

    // Implement Method --- getRootView()
    private MethodSpec generateGetRootViewMethod() {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ConnecteCreateModel.GetRootViewMethodName)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .returns(ClassName.bestGuess(createModel.getConnectorInfoModel().getK_Name()));

        methodBuilder.addCode("return this.pageRootView;\n");
        return methodBuilder.build();
    }

    // Implement Method --- getClickListener()
    private MethodSpec generateGetClickListenerMethod(boolean haveEvents) {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ConnecteCreateModel.GetClickListenerMethodName)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .returns(ClassName.bestGuess(createModel.getConnectorInfoModel().getL_Name()));

        if (haveEvents){
            methodBuilder.addCode("return this;\n");
        }else {
            methodBuilder.addCode("return null;\n");
        }
        return methodBuilder.build();
    }

    // Bind Views
    private MethodSpec generateBindViewMethod(ViewInfoModel viewInfoModel) {
        String attachParam = "false";

        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ConnecteCreateModel.BindViewsMethodName)
                .addModifiers(Modifier.PRIVATE)
                .addParameter(ClassName.bestGuess(createModel.getConnectorInfoModel().getT_Name()), "inflator")
                .addParameter(ClassName.bestGuess(createModel.getConnectorInfoModel().getK_Name()), "rootView")
                .returns(void.class);

        Element viewBinderElement = viewInfoModel.getViewBinderElement();
        if (viewBinderElement != null){
            String valueType = viewBinderElement.asType().toString();
            String valueName = viewBinderElement.getSimpleName().toString();
            methodBuilder.addCode("this.clazz.$L = $T.inflate(inflator,rootView,$L);\n",valueName, ClassName.bestGuess(valueType),attachParam);
            methodBuilder.addCode("this.pageRootView = this.clazz.$L.getRoot();\n",valueName);
        }
        return methodBuilder.build();
    }

    // Bind Processor
    private MethodSpec generateBindProcessorMethod(ProcessorInfoModel processorInfoModel) {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ConnecteCreateModel.BindProcessorMethodName)
                .addModifiers(Modifier.PRIVATE)
                .returns(void.class);

        String name = processorInfoModel.getProcessorElement().getSimpleName().toString();
        methodBuilder.addCode("this.clazz.$L = $N.$N(clazz);\n",name,"paramProvider",ProviderInterfaceCreateModel.ProvideProcessorMethodName);
        methodBuilder.addCode("(($L)this.clazz.$L).$N = $N.$N(clazz);\n",createModel.getProviderCreateModel().getProcessorClassElement().getSimpleName().toString(),
                name,createModel.getProviderCreateModel().getViewParamElement().getSimpleName(), "paramProvider",ProviderInterfaceCreateModel.ProvideViewMethodName);

        return methodBuilder.build();
    }

    // setOnClickListhener()
    private MethodSpec generateBindEventMethod(ViewInfoModel viewInfoModel) {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bindEvents")
                .addModifiers(Modifier.PRIVATE)
                .returns(void.class);

        String viewBinderName = viewInfoModel.getViewBinderElement().getSimpleName().toString();
        Map<String,Element> clickElementMap = viewInfoModel.getClickMethodElementMap();

        for (String methodName : clickElementMap.keySet()) {
            Element element = clickElementMap.get(methodName);
            int[] targetViewArray = element.getAnnotation(OnViewClick.class).value();
            for (int viewId:targetViewArray){
                String viewName = viewInfoModel.getViewIdMap().get(viewId);
                String viewRealName = ParseNameUtil.fromViewName(viewName);
                if (viewInfoModel.getViewBinderViewSet().contains(viewRealName)){
                    methodBuilder.addCode("this.clazz.$L.$L.setOnClickListener(this);\n",viewBinderName,viewRealName);
                }
            }
        }
        return methodBuilder.build();
    }

    // onClick()
    private MethodSpec generateOnClickMethod(ViewInfoModel viewInfoModel){
        final String viewStr = "android.view.View";
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ConnecteCreateModel.OnClickMethod)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ClassName.bestGuess(viewStr), "view")
                .returns(void.class);

        // Check If This Click Need To Intercept
        Element clickAspectElement = viewInfoModel.getClickAspectElement();
        if (clickAspectElement != null){
            boolean isStatic = false;
            for (Modifier modifier:clickAspectElement.getModifiers()){
                if (Modifier.STATIC == modifier){
                    isStatic = true;
                    break;
                }
            }

            String methodName = clickAspectElement.getSimpleName().toString();
            if (isStatic){
                String className = clickAspectElement.getEnclosingElement().asType().toString();

                methodBuilder.addCode("if($T.$L()){\n", ClassName.bestGuess(className), methodName);
                methodBuilder.addCode("\treturn;\n");
                methodBuilder.addCode("}\n");
            }else {
                methodBuilder.addCode("if(this.clazz.$L()){\n",methodName);
                methodBuilder.addCode("\treturn;\n");
                methodBuilder.addCode("}\n");
            }
        }

        // Real Operations
        Map<String,Element> clickElementMap = viewInfoModel.getClickMethodElementMap();
        methodBuilder.addCode("switch(view.getId()){\n");
        for (String methodName : clickElementMap.keySet()) {
            Element element = clickElementMap.get(methodName);
            OnViewClick onViewClick = element.getAnnotation(OnViewClick.class);
            int[] viewIdArray = onViewClick.value();
            for (int viewId:viewIdArray){
                methodBuilder.addCode("\tcase $L:\n",viewId);
                methodBuilder.addCode("\t\tthis.clazz.$L(view);\n",methodName);
                methodBuilder.addCode("\t\tbreak;\n");
            }
        }
        methodBuilder.addCode("\tdefault:\n");
        methodBuilder.addCode("\t\tbreak;\n");
        methodBuilder.addCode("}\n");
        return methodBuilder.build();
    }

    // Implement Method --- dispose()
    private MethodSpec generateDisposeMethod(ViewInfoModel viewInfoModel,ProcessorInfoModel processorInfoModel,boolean haveProvider) {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ConnecteCreateModel.DisposeMethodName)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC);

        methodBuilder.addCode("if(this.clazz != null){\n");
        Element processsorElement = processorInfoModel.getProcessorElement();
        if (processsorElement != null){
            String name = processsorElement.getSimpleName().toString();
            methodBuilder.addCode("\t(($L)this.clazz.$L).$N = null;\n",createModel.getProviderCreateModel().getProcessorClassElement().getSimpleName().toString(),name,createModel.getProviderCreateModel().getViewParamElement().getSimpleName());
            methodBuilder.addCode("\tthis.clazz.$L.dispose();\n",name);
            methodBuilder.addCode("\tthis.clazz.$L = null;\n",name);
        }

        Element viewBinderElement = viewInfoModel.getViewBinderElement();
        if (viewBinderElement != null){
            String name = viewBinderElement.getSimpleName().toString();
            methodBuilder.addCode("\tthis.clazz.$L = null;\n",name);
        }
        methodBuilder.addCode("}\n");

        methodBuilder.addCode("\n");
        if (haveProvider){
            methodBuilder.addCode("this.clazz.dispose();\n");
        }
        methodBuilder.addCode("this.clazz = null;\n");
        methodBuilder.addCode("this.pageRootView = null;\n");

        return methodBuilder.build();
    }

    public ConnecteCreateModel getCreateModel() {
        return createModel;
    }
}
